﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace PropertyExpression.ControllableQuery
{
    public class QueryContext
    {
        private readonly SqlCommand sqlCommand;
        private readonly SqlParameterNameGenerator sqlParameterNameGenerator = new SqlParameterNameGenerator();

        public QueryContext(SqlCommand sqlCommand)
        {
            this.sqlCommand = sqlCommand;
        }

        public IParam<Guid> CreateParam(Guid value)
        {
            return new Param<Guid>(this, parameterName => new SqlParameter(parameterName, value));
        }

        public IParam<string> CreateParam(string value)
        {
            return new Param<string>(this, parameterName => new SqlParameter(parameterName, value) { Size = -1 });
        }

        public IParam<int> CreateParam(int value)
        {
            return new Param<int>(this, parameterName => new SqlParameter(parameterName, value));
        }

        public IParam<ITableValued<T>> CreateParam<T>(IEnumerable<T> value)
        {
            return new Param<ITableValued<T>>(
                this,
                parameterName => new SqlParameter(parameterName, ConvertToDataTable(value))
                                     {
                                         SqlDbType = SqlDbType.Structured,
                                         TypeName = Util.GetSqlTableValuedParameterName<T>()
                                     }
                );
        }

        private class Param<T> : IParam<T>
        {
            private readonly QueryContext parent;
            private readonly Func<string, SqlParameter> sqlParameterFunc;

            public Param(QueryContext parent, Func<string, SqlParameter> sqlParameterFunc)
            {
                this.parent = parent;
                this.sqlParameterFunc = sqlParameterFunc;
            }

            public ISqlParam<T> GenerateSqlParam()
            {
                return GetSqlParam(parent.sqlParameterNameGenerator.GetNextName());
            }

            public ISqlParam<T> GetSqlParam(string parameterName)
            {
                return new SqlParam(this, parameterName);
            }

            private class SqlParam : ISqlParam<T>
            {
                private readonly Lazy<string> toSqlResult;

                public SqlParam(Param<T> parent, string parameterName)
                {
                    toSqlResult = new Lazy<string>(
                        () =>
                            {
                                parent.parent.sqlCommand.Parameters.Add(parent.sqlParameterFunc(parameterName));
                                return parameterName;
                            }
                        );
                }

                public string ToSql()
                {
                    return toSqlResult.Value;
                }

                public override string ToString()
                {
                    return ToSql();
                }
            }

            public override string ToString()
            {
                return this.ToSql();
            }
        }

        private static DataTable ConvertToDataTable<T>(IEnumerable<T> value)
        {
            var dataTable = new DataTable();
            var propertyInfos = typeof(T).GetProperties();
            foreach (var propertyInfo in propertyInfos)
            {
                dataTable.Columns.Add(propertyInfo.Name, propertyInfo.PropertyType);
            }
            foreach (var item in value)
            {
                foreach (var propertyInfo in propertyInfos)
                {
                    var dataRow = dataTable.NewRow();
                    dataRow[propertyInfo.Name] = propertyInfo.GetValue(item, null);
                    dataTable.Rows.Add(dataRow);
                }
            }
            return dataTable;
        }
    }
}